﻿/* Copyright (c) 2011-2012, Zetatron Consulting
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Zetatron Consulting nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenSSL.Core;
using OpenSSL.Crypto;
using System.IO;
using AionCxxLibrary.Utilities;

namespace AionCxxLibrary.Packets
{
    public abstract class AionCrypter
    {
        public abstract int Encrypt(ref byte[] data, int offset, int size);
        public abstract void Decrypt(ref byte[] data, int offset, int size);
    }

    public class AuthenticationCrypter : AionCrypter
    {
        static System.Random rnd = new System.Random();
        public RSA Rsa;
        public byte[] EncryptedRSAModulus;
        BlowfishCipher Cipher;

        byte[] login_key =
        {
            (byte) 0x6b, (byte) 0x60, (byte) 0xcb, (byte) 0x5b, (byte) 0x82, (byte) 0xce, (byte) 0x90, (byte) 0xb1,
            (byte) 0xcc, (byte) 0x2b, (byte) 0x6c, (byte) 0x55, (byte) 0x6c, (byte) 0x6c, (byte) 0x6c, (byte) 0x6c
        };
        public byte[] player_key = new byte[16];

        public AuthenticationCrypter()
        {
            rnd.NextBytes(player_key);
            Cipher = new BlowfishCipher(player_key);

            Rsa = new RSA();
            Rsa.GenerateKeys(1024, 65537, null, null);

            // Encrypt modulus
            EncryptedRSAModulus = Rsa.PublicModulus;            
            if ((EncryptedRSAModulus.Length == 0x81) && (EncryptedRSAModulus[0] == 0x00))
            {
                byte[] tmp = new byte[0x80];
                Array.Copy(EncryptedRSAModulus, 1, tmp, 0, 0x80);
                EncryptedRSAModulus = tmp;
            }
            for (int i = 0; i < 4; i++)
            {
                byte temp = EncryptedRSAModulus[i];
                EncryptedRSAModulus[i] = EncryptedRSAModulus[0x4d + i];
                EncryptedRSAModulus[0x4d + i] = temp;
            }
            for (int i = 0; i < 0x40; i++)
            {
                EncryptedRSAModulus[i] = (byte)(EncryptedRSAModulus[i] ^ EncryptedRSAModulus[0x40 + i]);
            }
            for (int i = 0; i < 4; i++)
            {
                EncryptedRSAModulus[0x0d + i] = (byte)(EncryptedRSAModulus[0x0d + i] ^ EncryptedRSAModulus[0x34 + i]);
            }

            for (int i = 0; i < 0x40; i++)
            {
                EncryptedRSAModulus[0x40 + i] = (byte)(EncryptedRSAModulus[0x40 + i] ^ EncryptedRSAModulus[i]);
            }
        }

        public override void Decrypt(ref byte[] data, int offset, int size)
        {
            Cipher.decipher(ref data, offset, size);
        }
        public override int Encrypt(ref byte[] data, int offset, int size)
        {
            size += 8 - size % 8;
            appendChecksum(ref data, offset, size);
            Cipher.cipher(ref data, offset, size);
            return size;
        }

        private void encXORPass(ref byte[] data, int offset, int length, int key)
        {
            int stop = length - 8;
            int pos = 4 + offset;
            int edx;
            int ecx = key;

            while (pos < stop)
            {
                edx = (data[pos] & 0xFF);
                edx |= (data[pos + 1] & 0xFF) << 8;
                edx |= (data[pos + 2] & 0xFF) << 16;
                edx |= (data[pos + 3] & 0xFF) << 24;
                ecx += edx;
                edx ^= ecx;
                data[pos++] = (byte)(edx & 0xFF);
                data[pos++] = (byte)(edx >> 8 & 0xFF);
                data[pos++] = (byte)(edx >> 16 & 0xFF);
                data[pos++] = (byte)(edx >> 24 & 0xFF);
            }

            data[pos++] = (byte)(ecx & 0xFF);
            data[pos++] = (byte)(ecx >> 8 & 0xFF);
            data[pos++] = (byte)(ecx >> 16 & 0xFF);
            data[pos] = (byte)(ecx >> 24 & 0xFF);
        }

        private void appendChecksum(ref byte[] raw, int offset, int length)
        {
            long chksum = 0;
            int count = length - 4;
            long ecx;
            int i;

            for (i = offset; i < count; i += 4)
            {
                ecx = raw[i] & 0xff;
                ecx |= raw[i + 1] << 8 & 0xff00;
                ecx |= raw[i + 2] << 0x10 & 0xff0000;
                ecx |= raw[i + 3] << 0x18 & 0xff000000;
                chksum ^= ecx;
            }

            ecx = raw[i] & 0xff;
            ecx |= raw[i + 1] << 8 & 0xff00;
            ecx |= raw[i + 2] << 0x10 & 0xff0000;
            ecx |= raw[i + 3] << 0x18 & 0xff000000;
            raw[i] = (byte)(chksum & 0xff);
            raw[i + 1] = (byte)(chksum >> 0x08 & 0xff);
            raw[i + 2] = (byte)(chksum >> 0x10 & 0xff);
            raw[i + 3] = (byte)(chksum >> 0x18 & 0xff);
        }

    }

    public class WorldCrypter : AionCrypter
    {
        public Crypt _crypt = new Crypt();
        
        public override void Decrypt(ref byte[] data, int offset, int size)
        {
            _crypt.decrypt(ref data, offset, size);
        }
        public override int Encrypt(ref byte[] data, int offset, int size)
        {
            _crypt.encrypt(ref data, offset, size);
            return size;
        }
    }

    public class BlowfishCipher
    {
        private static uint[] PARRAY_INIT =
    {
        0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, 0x452821E6,
        0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B
    };

        private static uint[] SBOX_INIT_0 =
    {
        0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947,
        0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658,
        0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918,
        0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
        0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF,
        0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C,
        0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60,
        0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
        0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2,
        0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176,
        0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F,
        0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
        0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6,
        0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C,
        0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39,
        0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
        0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB,
        0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8,
        0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC,
        0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
        0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB,
        0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777,
        0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81,
        0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
        0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B,
        0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9,
        0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476,
        0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
        0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A
    };
        /**
         * Constants - Pi value
         */
        private static uint[] SBOX_INIT_1 =
    {
        0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71,
        0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65,
        0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6,
        0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
        0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A,
        0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC,
        0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1,
        0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
        0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718,
        0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908,
        0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6,
        0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
        0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6,
        0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D,
        0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1,
        0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
        0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90,
        0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA,
        0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E,
        0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
        0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF,
        0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA,
        0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A,
        0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
        0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092,
        0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E,
        0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705,
        0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
        0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7
    };
        /**
         * Constants - Pi value
         */
        private static uint[] SBOX_INIT_2 =
    {
        0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471,
        0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF,
        0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6,
        0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
        0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35,
        0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332,
        0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7,
        0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
        0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE,
        0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60,
        0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62,
        0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
        0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60,
        0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3,
        0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF,
        0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
        0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659,
        0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086,
        0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187,
        0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
        0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E,
        0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09,
        0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F,
        0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
        0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7,
        0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188,
        0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3,
        0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
        0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0
    };
        /**
         * Constants - Pi value
         */
        private static uint[] SBOX_INIT_3 =
    {
        0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D,
        0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79,
        0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8,
        0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
        0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3,
        0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797,
        0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472,
        0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
        0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15,
        0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5,
        0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862,
        0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
        0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD,
        0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB,
        0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671,
        0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
        0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1,
        0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A,
        0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF,
        0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
        0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532,
        0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E,
        0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5,
        0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
        0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD,
        0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3,
        0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0,
        0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
        0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6
    };

        /**
         * Blowfish cipher secret key
         */
        private byte[] blowfishKey;
        /**
         * pArray
         */
        private int[] pArray;
        /**
         * sBoxes
         */
        private uint[][] sBoxes;

        /**
         * Default constructor. Initializes the Blowfish Cipher with blowfishKey
         * @param blowfishKey Blowfish Key
         */
        public BlowfishCipher(byte[] blowfishKey)
        {
            pArray = new int[18];
            sBoxes = new uint[4][];
            for (int i = 0; i < 4; i++)
            {
                sBoxes[i] = new uint[256];
            }
            updateKey(blowfishKey);
        }

        /**
         * Update current blowfish key with the new one and reinitialize the P-array and S-boxes
         * @param blowfishKey new blowfish key
         */
        public void updateKey(byte[] blowfishKey)
        {
            this.blowfishKey = blowfishKey;
            Array.Copy(SBOX_INIT_0, 0, sBoxes[0], 0, 256);
            Array.Copy(SBOX_INIT_1, 0, sBoxes[1], 0, 256);
            Array.Copy(SBOX_INIT_2, 0, sBoxes[2], 0, 256);
            Array.Copy(SBOX_INIT_3, 0, sBoxes[3], 0, 256);
            initArrays();
        }

        /**
         * Initialise the P-array and S-boxes
         */
        private void initArrays()
        {
            int keyIndex = 0;

            for (int i = 0; i < 18; i++)
            {
                int data = 0;

                for (int j = 0; j < 4; j++)
                {
                    data = (data << 8) | (blowfishKey[keyIndex++] & 0xFF);

                    if (keyIndex >= blowfishKey.Length)
                    {
                        keyIndex = 0;
                    }
                }

                pArray[i] = (int)PARRAY_INIT[i] ^ data;
            }

            byte[] b = new byte[8];

            for (int i = 0; i < 18; i += 2)
            {
                cipher(ref b);
                pArray[i] = byteArrayToInteger(b, 0);
                pArray[i + 1] = byteArrayToInteger(b, 4);
            }

            for (int i = 0; i < 4; i++)
            {
                initSBox(b, sBoxes[i]);
            }
        }

        /**
         * @param b
         * @param sBox
         */
        private void initSBox(byte[] b, uint[] sBox)
        {
            for (int j = 0; j < 256; j += 2)
            {
                cipher(ref b);
                sBox[j] = (uint)byteArrayToInteger(b, 0);
                sBox[j + 1] = (uint)byteArrayToInteger(b, 4);
            }
        }

        /**
         * Cipher the given byte-array with Blowfish cipher
         * @param data byte array to be ciphered
         */
        public void cipher(ref byte[] data)
        {
            cipher(ref data, 0, data.Length);
        }

        /**
         * Cipher the given byte-array with Blowfish cipher
         * @param data byte array to be ciphered
         * @param offset byte array offset
         * @param length byte array length
         */
        public void cipher(ref byte[] data, int offset, int length)
        {
            int blockNumber = length >> 3;
            int p;

            for (int k = 0; k < blockNumber; k++)
            {
                p = offset + (k << 3);

                int xl = byteArrayToInteger(data, p);
                int xr = byteArrayToInteger(data, p + 4);
                int tmp;

                for (int i = 0; i < 16; i++)
                {
                    xl = xl ^ pArray[i];
                    xr = F(xl) ^ xr;
                    tmp = xl;
                    xl = xr;
                    xr = tmp;
                }

                tmp = xl;
                xl = xr;
                xr = tmp;
                xr ^= pArray[16];
                xl ^= pArray[17];
                integerToByteArray(xl, data, p);
                integerToByteArray(xr, data, p + 4);
            }
        }

        /**
         * Decipher the given byte-array with Blowfish cipher
         * @param data byte array to be deciphered
         */
        public void decipher(ref byte[] data)
        {
            decipher(ref data, 0, data.Length);
        }

        /**
         * Decipher the given byte-array with Blowfish cipher
         * @param data byte array to be deciphered
         * @param offset byte array offset
         * @param length byte array length
         */
        public void decipher(ref byte[] data, int offset, int length)
        {
            int blocks = length >> 3;
            int p;

            for (int k = 0; k < blocks; k++)
            {
                p = offset + (k << 3);

                int lb = byteArrayToInteger(data, p);
                int rb = byteArrayToInteger(data, p + 4);
                int tmp;

                for (int i = 17; i > 1; i--)
                {
                    lb = lb ^ pArray[i];
                    rb = F(lb) ^ rb;
                    tmp = lb;
                    lb = rb;
                    rb = tmp;
                }

                tmp = lb;
                lb = rb;
                rb = tmp;
                rb ^= pArray[1];
                lb ^= pArray[0];
                integerToByteArray(lb, data, p);
                integerToByteArray(rb, data, p + 4);
            }
        }

        /**
         * The round (Feistel) function of Blowfish cipher
         * @param x
         * @return y
         */
        private int F(int x)
        {
            int a, b, c, d;

            d = x & 0xFF;
            x >>= 8;
            c = x & 0xFF;
            x >>= 8;
            b = x & 0xFF;
            x >>= 8;
            a = x & 0xFF;

            uint y = sBoxes[0][a] + sBoxes[1][b];

            y ^= sBoxes[2][c];
            y += sBoxes[3][d];

            return (int)y;
        }

        /**
         * Convert 4 bytes to Integer
         * @param b byte array (4 bytes)
         * @param offset byte array offset
         * @return integer value
         */
        private int byteArrayToInteger(byte[] b, int offset)
        {
            return (b[offset + 3] & 0xFF) << 24 | (b[offset + 2] & 0xFF) << 16 | (b[offset + 1] & 0xFF) << 8
                   | (b[offset] & 0xFF);
        }

        /**
         * Convert integer value to bytes
         * @param value integer value
         * @param b dest byte array
         * @param offset byte array offset
         */
        private void integerToByteArray(int value, byte[] b, int offset)
        {
            b[offset] = (byte)(value & 0xFF);
            b[offset + 1] = (byte)(value >> 8 & 0xFF);
            b[offset + 2] = (byte)(value >> 16 & 0xFF);
            b[offset + 3] = (byte)(value >> 24 & 0xFF);
        }
    }

    public class EncryptionKeyPair
    {
        static int SERVER = 0;
        static int CLIENT = 1;
        static byte[] staticKey = Encoding.UTF8.GetBytes("nKO/WctQ0AVLbpzfBkS6NevDYT8ourG5CRlmdjyJ72aswx4EPq1UgZhFMXH?3iI9");
        private int baseKey = 0;
        private byte[][] keys = null;
        public EncryptionKeyPair(int baseKey)
        {
            this.baseKey = baseKey;
            this.keys = new byte[2][];
            this.keys[SERVER] = new byte[] {
			    (byte) (baseKey & 0xff),
			    (byte) ((baseKey >> 8) & 0xff),
			    (byte) ((baseKey >> 16) & 0xff),
			    (byte) ((baseKey >> 24) & 0xff),
			    (byte) 0xa1,
			    (byte) 0x6c,
			    (byte) 0x54,
			    (byte) 0x87
		    };
            this.keys[CLIENT] = new byte[this.keys[SERVER].Length];
            Array.Copy(this.keys[SERVER], 0, this.keys[CLIENT], 0, this.keys[SERVER].Length);
        }
        public int getBaseKey()
        {
            return baseKey;
        }
        public bool decrypt(ref byte[] data, int arrayIndex, int size)
        {
            byte[] clientPacketKey = keys[CLIENT];
            int prev = data[arrayIndex];
            data[arrayIndex++] ^= (byte)(clientPacketKey[0] & 0xff);
            for (int i = 1; i < size; i++, arrayIndex++)
            {
                int curr = data[arrayIndex] & 0xff;
                data[arrayIndex] ^= (byte)((staticKey[i & 63] & 0xff) ^ (clientPacketKey[i & 7] & 0xff) ^ prev);
                prev = curr;
            }

            long oldKey = (((long)clientPacketKey[0] & 0xff) << 0) | (((long)clientPacketKey[1] & 0xff) << 8)
            | (((long)clientPacketKey[2] & 0xff) << 16) | (((long)clientPacketKey[3] & 0xff) << 24)
            | (((long)clientPacketKey[4] & 0xff) << 32) | (((long)clientPacketKey[5] & 0xff) << 40)
            | (((long)clientPacketKey[6] & 0xff) << 48) | (((long)clientPacketKey[7] & 0xff) << 56);

            oldKey += size;

            if (data[6] == (0xFF - data[3]))
            {
                clientPacketKey[0] = (byte)(oldKey >> 0 & 0xff);
                clientPacketKey[1] = (byte)(oldKey >> 8 & 0xff);
                clientPacketKey[2] = (byte)(oldKey >> 16 & 0xff);
                clientPacketKey[3] = (byte)(oldKey >> 24 & 0xff);
                clientPacketKey[4] = (byte)(oldKey >> 32 & 0xff);
                clientPacketKey[5] = (byte)(oldKey >> 40 & 0xff);
                clientPacketKey[6] = (byte)(oldKey >> 48 & 0xff);
                clientPacketKey[7] = (byte)(oldKey >> 56 & 0xff);
            }
            else
            {
                Log.Error("Cryptographically incorrect packet received !");
                Log.Debug("Data: " + Utility.ToHexString(data, true));
            }
            return true;
        }

        public void encrypt(ref byte[] data, int arrayIndex, int size)
        {
            byte[] serverPacketKey = keys[SERVER];
            data[arrayIndex] ^= (byte)(serverPacketKey[0] & 0xff);
            int prev = data[arrayIndex++];
            for (int i = 1; i < size; i++, arrayIndex++)
            {
                data[arrayIndex] ^= (byte)((staticKey[i & 63] & 0xff) ^ (serverPacketKey[i & 7] & 0xff) ^ prev);
                prev = data[arrayIndex];
            }

            long oldKey = (((long)serverPacketKey[0] & 0xff) << 0) | (((long)serverPacketKey[1] & 0xff) << 8)
                | (((long)serverPacketKey[2] & 0xff) << 16) | (((long)serverPacketKey[3] & 0xff) << 24)
                | (((long)serverPacketKey[4] & 0xff) << 32) | (((long)serverPacketKey[5] & 0xff) << 40)
                | (((long)serverPacketKey[6] & 0xff) << 48) | (((long)serverPacketKey[7] & 0xff) << 56);

            oldKey += size;

            serverPacketKey[0] = (byte)(oldKey >> 0 & 0xff);
            serverPacketKey[1] = (byte)(oldKey >> 8 & 0xff);
            serverPacketKey[2] = (byte)(oldKey >> 16 & 0xff);
            serverPacketKey[3] = (byte)(oldKey >> 24 & 0xff);
            serverPacketKey[4] = (byte)(oldKey >> 32 & 0xff);
            serverPacketKey[5] = (byte)(oldKey >> 40 & 0xff);
            serverPacketKey[6] = (byte)(oldKey >> 48 & 0xff);
            serverPacketKey[7] = (byte)(oldKey >> 56 & 0xff);
        }
    }

    public class Crypt
    {
        public static byte staticServerPacketCode = 0x54;// 2.7.0.x
        private bool isEnabled;
        private EncryptionKeyPair packetKey = null;
        private static System.Random rnd = new System.Random();

        public int enableKey()
        {
            if (packetKey != null)
                return 0;
            int key = rnd.Next();
            packetKey = new EncryptionKeyPair(key);
            return (int)((key ^ 0xCD92E451) + 0x3FF2CC87);
        }

        public bool decrypt(ref byte[] data, int offset, int len)
        {
            try
            {
                if (!isEnabled)
                {
                    return false;
                }
                packetKey.decrypt(ref data, offset, len);
            }
            catch (Exception e)
            {
                Log.Error("Decryption error", e);
            }
            return false;
        }

        public void encrypt(ref byte[] data, int offset, int len)
        {
            if (!isEnabled)
            {
                isEnabled = true;
                return;
            }
            try
            {
                packetKey.encrypt(ref data, offset, len);
            }
            catch (Exception e)
            {
                Log.Error("Encryption error", e);
            }
        }

        public static byte encodeOpcode(int op)
        {
            return (byte)((op + 0xAE) ^ 0xEE);
        }
    }

}
